﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Wisdom.Core;


namespace Wisdom.Core
{
    /// <summary>
    /// Defines a base class which is used to execute application startup and cleanup tasks.
    /// </summary>
    public abstract class Bootstrapper : Disposable, IBootstrapper
    {
        private IObjectContainer container;
        private static readonly Type moduleType = typeof(IModule);


        /// <summary>
        /// Initializes a new instance of the <see cref="Bootstrapper"/> class.
        /// </summary>
        /// <param name="objectContainer">The object container.</param>
        /// <param name="buildManager">The build manager.</param>
        /// <param name="bootstrapperTasksRegistry">The bootstrapper tasks.</param>
        /// <param name="perRequestTasksRegistry">The per request tasks.</param>
        protected Bootstrapper(IObjectContainer objectContainer, IBuildManager buildManager, IBootstrapperTasksRegistry bootstrapperTasksRegistry, IPerRequestTasksRegistry perRequestTasksRegistry)
        {
            Guard.IsNotNull(objectContainer, "objectContainer");
            Guard.IsNotNull(buildManager, "buildManager");
            Guard.IsNotNull(bootstrapperTasksRegistry, "bootstrapperTasksRegistry");
            Guard.IsNotNull(perRequestTasksRegistry, "perRequestTasksRegistry");

            BuildManager = buildManager;
            BootstrapperTasks = bootstrapperTasksRegistry;
            PerRequestTasks = perRequestTasksRegistry;
            RegistAndSetContainer(objectContainer);
        }

        /// <summary>
        /// Current bootstrapper
        /// </summary>
        public static IBootstrapper Current { get; protected set; }

        /// <summary>
        /// Gets  the build manager.
        /// </summary>
        /// <value>The build manager.</value>
        public IBuildManager BuildManager { get; private set; }

        /// <summary>
        /// Gets the bootstrapper task registry.
        /// </summary>
        /// <value>The bootstrapper tasks.</value>
        public IBootstrapperTasksRegistry BootstrapperTasks { get; private set; }

        /// <summary>
        /// Gets the per request task registry.
        /// </summary>
        /// <value>The per request tasks.</value>
        public IPerRequestTasksRegistry PerRequestTasks { get; private set; }
        

        /// <summary>
        /// Gets the objectContainer.
        /// </summary>
        /// <value>The objectContainer.</value>
        public IObjectContainer Container
        {
            [DebuggerStepThrough]
            get
            {
                return container;
            }
        }

        /// <summary>
        /// Executes the <seealso cref="IBootstrapperTask"/>.
        /// </summary>
        public void ExecuteBootstrapperTasks()
        {
            Execute<IBootstrapperTask>(BootstrapperTasks.TaskConfigurations);
        }

        /// <summary>
        /// Dispose the <seealso cref="IBootstrapperTask"/>.
        /// </summary>
        public void DisposeBootstrapperTasks()
        {
            Cleanup<IBootstrapperTask>(BootstrapperTasks.TaskConfigurations);
        }

        /// <summary>
        /// Executes the <seealso cref="IPerRequestTask"/>.
        /// </summary>
        public void ExecutePerRequestTasks()
        {
            Execute<IPerRequestTask>(PerRequestTasks.TaskConfigurations);
        }

        /// <summary>
        /// Dispose the <seealso cref="IPerRequestTask"/>.
        /// </summary>
        public void DisposePerRequestTasks()
        {
            Cleanup<IPerRequestTask>(PerRequestTasks.TaskConfigurations);
        }

        private void Cleanup<TTask>(IEnumerable<KeyValuePair<Type, Action<object>>> tasks) where TTask : ITask
        {
            foreach (var task in tasks.Select(taskConfiguration => (TTask)Container.GetService(taskConfiguration.Key)))
            {
                task.Dispose();
            }
        }

        private void RegistAndSetContainer(IObjectContainer objectContainer)
        {
            this.container = objectContainer;

            Register(objectContainer);

            SetDependencyResolver(objectContainer);

            LoadModules(objectContainer);
        }


        private void Execute<TTask>(IEnumerable<KeyValuePair<Type, Action<object>>> tasks) where TTask : ITask
        {
            var shouldSkip = false;

            foreach (var taskConfiguration in tasks)
            {
                if (shouldSkip)
                {
                    shouldSkip = false;
                    continue;
                }

                var task = (TTask)Container.GetService(taskConfiguration.Key);

                Debug.Assert(task != null, "Task should be not null");

                if (taskConfiguration.Value != null)
                {
                    taskConfiguration.Value(task);
                }

                var continuation = task.Execute();

                if (continuation == TaskContinuation.Break)
                {
                    break;
                }

                shouldSkip = continuation == TaskContinuation.Skip;
            }
        }



        /// <summary>
        /// Loads the objectContainer specific modules.
        /// </summary>
        protected void LoadModules(IObjectContainer objectContainer)
        {
            if (BuildManager != null)
            {
                BuildManager.ConcreteTypes
                            .Where(type => moduleType.IsAssignableFrom(type) && type.HasDefaultConstructor())
                            .Select(Activator.CreateInstance)
                            .Cast<IModule>()
                            .Each(module => module.Load(container));
            }
        }

        protected override void DisposeCore()
        {
            if (container != null)
            {
                container.Dispose();
            }
            this.DisposeBootstrapperTasks();
            this.DisposePerRequestTasks();
        }

        protected abstract void Register(IObjectContainer objectContainer);

        protected abstract void SetDependencyResolver(IObjectContainer objectContainer);
    }
}